//////////////////////////////////////////////
// ArrayWrapper.h
//
//////////////////////////////////////////////

/// Defines / Macros -------------------------

#pragma once

/// Forward decl -----------------------------

namespace nkScripts
{
	class Environment ;
}

/// Includes ---------------------------------

// nkAstraeus
#include "../../../Dll/DllDefines.h"

// nkScripts
#include <NilkinsScripts/Environments/Functions/Function.h>

#include <NilkinsScripts/Environments/UserTypes/ArrayAccessorDescriptor.h>
#include <NilkinsScripts/Environments/UserTypes/UserType.h>
#include <NilkinsScripts/Environments/UserTypes/UserTypeFieldDescriptor.h>

#include <NilkinsScripts/Environments/Environment.h>

// Standards
#include <array>

/// Template base ----------------------------

namespace nkAstraeus::stdWrap
{
	template <typename T, int S>
	class ArrayWrapperTemplate final
	{
		public :

			// Utils
			static nkScripts::FUNCTION_PARAMETER_TYPE getNkType ()
			{
				if constexpr (std::is_same<T, float>::value)
					return nkScripts::FUNCTION_PARAMETER_TYPE::FLOAT ;
				else if constexpr (std::is_same<T, double>::value)
					return nkScripts::FUNCTION_PARAMETER_TYPE::DOUBLE ;
				else if constexpr (std::is_integral<T>::value)
					return nkScripts::FUNCTION_PARAMETER_TYPE::INT ;
				else if constexpr (std::is_same<T, std::string>::value)
					return nkScripts::FUNCTION_PARAMETER_TYPE::STRING ;

				return nkScripts::FUNCTION_PARAMETER_TYPE::VOID ;
			}

			// Registering
			static void updateEnvironmentPod (nkScripts::Environment* env, const nkMemory::StringView& typeName)
			{
				// Get type
				nkScripts::UserType* type = env->getUserType(typeName) ;

				// Constructor, destructor
				type->setConstructor(&constructor) ;
				type->setDestructor(&destructor) ;

				// Register functions
				nkScripts::Function* func = nullptr ;

				// Getters
				func = type->addMethod("size") ;
				func->setFunction(&size) ;

				// Operators
				nkScripts::ArrayAccessorDescriptor arrayDesc ;
				arrayDesc._fieldType = getNkType() ;
				arrayDesc._readFunc = readIndexPod ;
				arrayDesc._writeFunc = writeIndexPod ;
				type->enableArrayIndexing(arrayDesc) ;
			}

			static void updateEnvironmentClass (nkScripts::Environment* env, const nkMemory::StringView& typeName, const nkMemory::StringView& containedType)
			{
				// Get type
				nkScripts::UserType* type = env->getUserType(typeName) ;

				// Constructor, destructor
				type->setConstructor(&constructor) ;
				type->setDestructor(&destructor) ;

				// Register functions
				nkScripts::Function* func = nullptr ;

				// Getters
				func = type->addMethod("size") ;
				func->setFunction(&size) ;

				// Operators
				auto readFunc = [containedType] (const nkScripts::DataStack& stack) -> nkScripts::OutputValue
					{
						return readIndexClass(stack, containedType) ;
					} ;

				nkScripts::ArrayAccessorDescriptor arrayDesc ;
				arrayDesc._fieldType = nkScripts::FUNCTION_PARAMETER_TYPE::USER_DATA_PTR ;
				arrayDesc._userTypeName = containedType ;
				arrayDesc._readFunc = readFunc ;
				arrayDesc._writeFunc = writeIndexClass ;
				type->enableArrayIndexing(arrayDesc) ;
			}

			// Constructor, destructor
			static void* constructor (const nkScripts::DataStack& stack)
			{
				return new std::array<T, S> () ;
			}

			static void destructor (void* data)
			{
				delete (std::array<T, S>*)data ;
			}

			// Getters
			static nkScripts::OutputValue size (const nkScripts::DataStack& stack)
			{
				std::array<T, S>* dataCast = (std::array<T, S>*)stack[0]._valUser._userData ;
				return nkScripts::OutputValue((int)dataCast->size()) ;
			}

			// Operators
			static nkScripts::OutputValue readIndexPod (const nkScripts::DataStack& stack)
			{
				std::array<T, S>* dataCast = (std::array<T, S>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;

				return nkScripts::OutputValue((T)(*dataCast)[index - 1]) ;
			}

			static nkScripts::OutputValue readIndexClass (const nkScripts::DataStack& stack, const nkMemory::StringView& typeName)
			{
				std::array<T, S>* dataCast = (std::array<T, S>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;

				return nkScripts::OutputValue(&(*dataCast)[index - 1], typeName, false) ;
			}

			static void writeIndexPod (const nkScripts::DataStack& stack)
			{
				// Cast input
				std::array<T, S>* dataCast = (std::array<T, S>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt - 1 ;

				// Check according to type
				if constexpr (std::is_same<T, float>::value)
					(*dataCast)[index] = stack[2]._valFloat ;
				else if constexpr (std::is_same<T, double>::value)
					(*dataCast)[index] = stack[2]._valDouble ;
				else if constexpr (std::is_integral<T>::value)
					(*dataCast)[index] = stack[2]._valInt ;
				else if constexpr (std::is_same<T, std::string>::value)
					(*dataCast)[index] = stack[2]._valString ;
			}

			static void writeIndexClass (const nkScripts::DataStack& stack)
			{
				// Cast input
				std::array<T, S>* dataCast = (std::array<T, S>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt - 1 ;

				// Get type and assign it
				T* input = (T*)stack[2]._valUser._userData ;
				(*dataCast)[index] = *input ;
			}
	} ;
}

/// Class ------------------------------------

namespace nkAstraeus::stdWrap
{
	class DLL_ASTRAEUS_EXPORT ArrayWrapper final
	{
		public :

			// Environment update
			static void updateEnvironment (nkScripts::Environment* env) ;
	} ;
}